/**
 * PHOSIDE: PHosphorylation Site IDentification Engine.
 * Copyright 2009 Digital Biology Lab, University of Missouri.
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 3 of the License, or (at your option) any later
 * version. <p/> This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more
 * details.
 */

package phoside.classify;

import phoside.data.feature.model.Instance;

import phoside.util.SamplingUtil;


import java.io.Serializable;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author gjj
 */
public class BootstrapBinaryClassifier implements BinaryClassifier, Serializable {
    private static final long serialVersionUID = -3743058559473448205L;
    
    protected final BinaryClassifier actualClassifier;
    transient protected final int nboots_pos;
    transient protected final int nboots_neg;
    transient protected final List<TrainingDataChangeHandler> trainingDataChangeHandlers;

    public BootstrapBinaryClassifier(final BinaryClassifier actualClassifier,
                                     final int nboots) {
        this(actualClassifier, nboots, nboots);
    }

    public BootstrapBinaryClassifier(final BinaryClassifier actualClassifier,
                                     final int nboots_pos,
                                     final int nboots_neg) {
        if (actualClassifier==null) {
            throw new java.lang.NullPointerException();
        }

        this.actualClassifier = actualClassifier;
        this.nboots_pos = nboots_pos;
        this.nboots_neg = nboots_neg;
        trainingDataChangeHandlers = new ArrayList();
    }

    public void addTrainingDataChangeHandler(final TrainingDataChangeHandler handler) {
        if (handler==null) {
            return;
        }
        
        trainingDataChangeHandlers.add(handler);
    }

    /**
     *
     * @param instances
     * @return a List of predicted values
     */
    public List<Double> classify(List<Instance> instances) {
        return actualClassifier.classify(instances);
    }

    /**
     *
     * @param instances
     * @param label
     * @return
     */
    public boolean train(final List<Instance> instances, final List<Double> label) {
        if (instances==null || label==null) {
            throw new NullPointerException();
        }

        if (instances.size() != label.size()) {
            throw new IllegalArgumentException("Each instance should have exact one lable.");
        }
        
        List<Instance> ins = bootstrap(instances, label, true);        
        ins.addAll(bootstrap(instances, label, false));
        
        List<Double> l = new ArrayList(nboots_pos+nboots_neg);
        for (int i=0; i<nboots_pos; i++) {
            l.add(1.0);
        }
        for (int i=0; i<nboots_neg; i++) {
            l.add(-1.0);
        }

        for (TrainingDataChangeHandler handler : trainingDataChangeHandlers) {
            handler.trainingDataChanged(ins, l);
        }

        return actualClassifier.train(ins, l);
    }

    protected List<Instance> bootstrap(final List<Instance> instances,
                                     final List<Double> label,
                                     boolean positive) {
        List<Double> weight = new ArrayList();
        List<Instance> candidate = new ArrayList();
        int n = label.size();
        if (positive) {
            for (int i=0; i<n; i++) {
                Double l = label.get(i);
                if (l>0) {
                    weight.add(l);
                    candidate.add(instances.get(i));
                }
            }
            return SamplingUtil.weightedResampleWithReplacement(candidate, weight, nboots_pos);
        } else {
            for (int i=0; i<n; i++) {
                Double l = label.get(i);
                if (l<0) {
                    weight.add(-l);
                    candidate.add(instances.get(i));
                }
            }
            return SamplingUtil.weightedResampleWithReplacement(candidate, weight, nboots_neg);
        }

    }
}
